/*
 * Copyright 2007 by egnite Software GmbH
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * For additional information see http://www.ethernut.de/
 */

#include <dev/board.h>

#include <stdio.h>
#include <io.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include <arpa/inet.h>
#include <net/route.h>
#include <pro/dhcp.h>

#include <sys/version.h>
#include <sys/timer.h>

#include <pro/snmp_config.h>
#include <pro/snmp_mib.h>
#include <pro/snmp_api.h>
#include <pro/snmp_agent.h>
#include "mib2sys.h"
#include "mib2if.h"
#include "agent.h"

/*!
 * \example snmpd/snmpd.c
 *
 * Basic SNMP Agent.
 */
static char *version = "0.2.0";

/*! \brief Default MAC.
 *  Used if EEPROM is empty. */
#define MYMAC   0x00, 0x06, 0x98, 0x33, 0x44, 0x00

/*! \brief Default local IP address.
 *  Used if EEPROM configuration and DHCP is unavailable. */
#define MYIP    "10.10.34.2"

/*! \brief Default IP mask.
 *  Used if EEPROM configuration and DHCP is unavailable. */
#define MYMASK  "255.255.255.0"

/*! \brief Default gateway address.
 *  Used if EEPROM configuration and DHCP is unavailable.
 *  Only required if syslog or time server are located in
 *  a different network. */
#define MYGATE  "192.168.192.1"
//#define MYGATE  "10.10.22.1"

/*! \brief Device name used for UART output. */
//#define MYUART  DEV_DEBUG_NAME
#define MYUART  DEV_UART_NAME

/*! \brief Output device. */
//#define MYDEV   DEV_DEBUG
#define MYDEV   DEV_UART

/*! \brief UART baudrate. */
#define MYBAUD  115200

/* Determine the compiler. */
#if defined(__IMAGECRAFT__)
#if defined(__AVR__)
#define COMPILERNAME   "ICCAVR"
#else
#define COMPILERNAME   "ICC"
#endif
#elif defined(__GNUC__)
#if defined(__AVR__)
#define COMPILERNAME   "AVRGCC"
#elif defined(__arm__)
#define COMPILERNAME   "ARMGCC"
#else
#define COMPILERNAME   "GCC"
#endif
#else
#define COMPILERNAME   "Compiler unknown"
#endif

/* Result codes. */
#define UART_OK     0x0001
#define STDOUT_OK   0x0002
#define STDERR_OK   0x0004
#define BAUDRATE_OK 0x0008
#define LANDEV_OK   0x0010
#define NETIF_OK    0x0020
#define NETROUTE_OK 0x0040
#define TIMED_OK    0x0080
#define STDIN_OK   	0x0100


static u_char *MibVarsSysGet(CONST SNMPVAR * vp, OID * name, size_t * namelen, int exact, size_t * varlen, WMETHOD ** wmethod);
static int MibVarsSysSet(int action, u_char * var_val, u_char var_val_type, size_t var_val_len, OID * name, size_t name_len);

//static OID base_oid[] = { /*1, 3, 6, 1, 3, 55, 0*/SNMP_OID_MIB2, 3 };
static SNMPVAR mib_variables[] = {
	{MAG_CUSTOM_MESSAGE, ASN_OCTET_STR, ACL_RWRITE, MibVarsSysGet, 1, {0}},
	{MAG_CUSTOM_USERNAME, ASN_OCTET_STR, ACL_RONLY, MibVarsSysGet, 1, {1}},
};

#include "..\ai\data\ChatRules.inl"

#define USERNAME "BotMakers"

static char message[SNMP_MAX_LEN];
UDPSOCKET *sock;

NameRegistry *names;
int nameLen;
int nameMaxLen;
int requestName;
AIContext *ctx;


THREAD(aiRun, arg)
{
    printf("\nStarting SNMP agent...");

    {
	ctx = aiCreateContext(chatRules);

	pANTLR3_UINT8 greeting = aiGetRandomGreeting(ctx);
	if(greeting)
		aiSay((char*)greeting);
    }

    /* Run agent. */
    SnmpBotmakersAgent(sock);

	aiDestroyContext(ctx);

    /* Program stopped. */
    NutUdpDestroySocket(sock);
}

int main(void)
{
    OID view_all[] = { SNMP_OID_INTERNET };
    int view_idx;
    u_long baud = MYBAUD;
    u_char mac[6] = { MYMAC };
    int rc = 0;

    /*
     * Register UART devices, assign stdout and stderr to this device
     * and set the baudrate.
     */
    if (NutRegisterDevice(&MYDEV, 0, 0) == 0) {
        rc |= UART_OK;
        if (freopen(MYUART, "w", stdout)) {
            rc |= STDOUT_OK;
            if (_ioctl(_fileno(stdout), UART_SETSPEED, &baud) == 0) {
                rc |= BAUDRATE_OK;
            }
        }
        if (freopen(MYUART, "r", stdin)) {
            rc |= STDIN_OK;
        }
        if (freopen(MYUART, "w", stderr)) {
            rc |= STDERR_OK;
        }
    }

    /*
     * Print banner.
     */
    if (rc & STDOUT_OK) {
        printf("\n\nSNMP Agent %s\nNut/OS %s\n", version, NutVersionString());
        puts("Compiled by " COMPILERNAME);
        puts("Configure network");
    }

    /*
     * Register LAN device and configure network interface.
     */
    if (NutRegisterDevice(&DEV_ETHER, 0x8300, 5) == 0) {
        rc |= LANDEV_OK;
        if (NutNetIfConfig("eth0", mac, inet_addr(MYIP), inet_addr(MYMASK)) == 0) {
            rc |= NETIF_OK;
#ifdef MYGATE
            if (NutIpRouteAdd(0, 0, inet_addr(MYGATE), &DEV_ETHER) == 0) {
                rc |= NETROUTE_OK;
            }
#endif
        }
    }

    sock = NutUdpCreateSocket(SNMP_PORT);
    /* Nut/Net doesn't provide UDP datagram buffering by default. */
    {
        u_short max_ms = SNMP_MAX_MSG_SIZE * 2;
        NutUdpSetSockOpt(sock, SO_RCVBUF, &max_ms, sizeof(max_ms));
    }

    /* Register system variables. */
    if (MibRegisterSysVars()) {
        printf("Failed to register MibSys\n");
        for (;;)
            NutSleep(1000);
    }
    /* Register interface variables. */
    if (MibRegisterIfVars()) {
        printf("Failed to register MibIf\n");
        for (;;)
            NutSleep(1000);
    }

	if (SnmpMibRegister(base_oid, base_oidlen, mib_variables, sizeof(mib_variables) / sizeof(SNMPVAR))) {
		printf("Failed to register OID\n");
		for (;;)
			NutSleep(1000);
	}

    /* Create views. */
    if ((view_idx = SnmpViewCreate("all", view_all, sizeof(view_all), SNMP_VIEW_INCLUDED)) <= 0) {
        printf("Failed to create view\n");
        for (;;)
            NutSleep(1000);
    }
    /* Create communities. */
    if (SnmpCommunityCreate("public", view_idx, view_idx) || SnmpCommunityCreate("private", view_idx, view_idx)) {
        printf("Failed to create communities\n");
        for (;;)
            NutSleep(1000);
    }

    NutThreadCreate("aiRun", aiRun, 0, 2*1024);

    for (;;) {
        NutSleep(100);
    }

    return 0;
}

static int MibVarsSysSet(int action, u_char * var_val, u_char var_val_type, size_t var_val_len, OID * name, size_t name_len)
{
    size_t len = SNMP_MAX_LEN;
    u_char *value = message;
    u_char *cp;
    size_t size;

    if (action != SNMP_ACT_COMMIT) {
        return 0;
    }

    if (var_val_type != ASN_OCTET_STR) {
        return SNMP_ERR_WRONGTYPE;
    }
    if (var_val_len > MAX_SYSSTR_LEN) {
        return SNMP_ERR_WRONGLENGTH;
    }
    size = MAX_SYSSTR_LEN;
/*    if ((value = (u_char *)malloc(MAX_SYSSTR_LEN) + 1) == NULL) {
        return SNMP_ERR_RESOURCEUNAVAILABLE;
    }*/
    AsnOctetStringParse(var_val, &len, &var_val_type, value, &size);
    value[size] = 0;
    for (cp = value; *cp; cp++) {
        if (!isprint(*cp)) {
            //free(value);
            return SNMP_ERR_WRONGVALUE;
        }
    }
    if (action == SNMP_ACT_COMMIT) {
        switch (name[7]) {
        case 0:
		{
            //strcpy(message, value);
        	NameRegistry *n = SnmpFindNameByIP(senderIP);
            //printf("\n\nSenderIP is %s", inet_ntoa(senderIP));fflush(stdout);
            //SnmpDisplayNames();

        	SnmpLogMessage(value, n ? n->name : NULL);
        	if (n == NULL) {
        		requestName = 1;
        	}

        	{
        		struct AISentence *sentence;

        		sentence = aiCreateSentence(value);
        		if(!sentence)
        			break;

        		{
        			struct AIRule *rule = aiGetMatchingRule(ctx, sentence);
        			if(rule)
        			{
        				aiExecuteRule(ctx, rule, sentence);
        			}
        			else
        			{
        				aiSayRandomFallback(ctx, sentence);
        			}
        		}

        		aiDestroySentence(sentence);
        	}

		}
		break;
        }
    }
    //free(value);

    return 0;
}

/*!
 * \brief Access the specified MIB variable.
 *
 * \param vp
 * \param name    Contains the name to look for, either exact or one that
 *                is in front. On return the exact name is stored here.
 * \param namelen Number of sub identifiers in the name upon entry. On
 *                return the length of the exact name is stored here.
 * \param exact   If not zero, the name must match exactly. Otherwise we
 *                want the first name that is following the given one.
 * \param varlen  Size of the variable.
 */
static u_char *MibVarsSysGet(CONST SNMPVAR * vp, OID * name, size_t * namelen, int exact, size_t * varlen, WMETHOD ** wmethod)
{
    static u_char empty[1];
    int rc;
    OID *fullname;
    size_t fullnamelen = base_oidlen + vp->var_namelen/* + 1*/;

    fullname = malloc(fullnamelen * sizeof(OID));
    memcpy(fullname, base_oid, base_oidlen * sizeof(OID));
    memcpy(fullname + base_oidlen, vp->var_name, vp->var_namelen * sizeof(OID));
    //*(fullname + fullnamelen - 1) = 0;

    rc = SnmpOidCmp(name, *namelen, fullname, fullnamelen);
    if ((exact && rc) || (!exact && rc >= 0)) {
        free(fullname);
        return NULL;
    }
    memcpy(name, fullname, fullnamelen * sizeof(OID));
    free(fullname);
    *namelen = fullnamelen;

    *wmethod = NULL;
    *varlen = sizeof(long);
    switch (vp->var_magic) {
    case MAG_CUSTOM_USERNAME:
        *varlen = strlen(USERNAME);
        return (u_char *) USERNAME;

    case MAG_CUSTOM_MESSAGE:
        *wmethod = MibVarsSysSet;
		*varlen = strlen(message);
		return (u_char *) message;
    }
    return NULL;
}

NameRegistry *SnmpFindNameByIP(uint32_t ipAddr) {
	int i;
	for(i = 0; i < nameLen; i++) {
		if (names[i].ipAddr == ipAddr) {
			return &names[i];
		}
	}
	return NULL;
}

void SnmpDisplayNames(void) {
	int i;
    printf("\nBeginNames %u", nameLen);fflush(stdout);
	for(i = 0; i < nameLen; i++) {
		printf("\n%s: %s", names[i].name, inet_ntoa(names[i].ipAddr));fflush(stdout);
	}
    printf("\nEndNames");fflush(stdout);
}

void aiSay(char *text)
{
	if(text) {
    	SnmpSendBroadcastMessage(sock, text, USERNAME);
	}
}
